package biback.domain.ledger

import biback.domain._
import biback.domain.common.model.{Branch, BranchLevel}
import biback.domain.common.BranchAlgebra
import biback.domain.customer.BalanceChanged
import biback.utils.DomainLogger.DebugArgs
import biback.utils._
import cats.Monad

class LedgerService[F[_] : Monad](algebras: LedgerAlgebra[F] with BranchAlgebra[F]) extends GeneralAlgebra[F] {

  import AccountingAmountType._
  import AccountingBranchType._
  import algebras.branch._
  import algebras.ledger._

  implicit val o1: Ordering[BranchLevel] = (x: BranchLevel, y: BranchLevel) => y.level.value - x.level.value

  def onEvent(event: DomainEvent): LogErrOrF[AccountingCommand] = {
    event match {
      case e: BalanceChanged => onAcctingEvent(e)
      case _ => errF(NonAccountingForEvent(event))
    }
  }

  private def onAcctingEvent(e: BalanceChanged): LogErrOrF[AccountingCommand] = {
    val amounts = Map[AccountingAmountType, BiDec](
      Amount -> e.amount,
      Fee -> e.fee,
      NetAmount -> (e.amount - e.fee)
    )
    def peerAccting(a: Accounting, ledgers: List[Ledger]) =
      (ledgers.find(_.subjectNo == a.subjectNo).get, amounts(a.amtType))
    def levelOf(branches: List[Branch], brcNo: BranchNo): BranchLevel = branches.find(_.no == brcNo).get.level
    def transit(brcNoSet: Set[BranchNo], one: LedgerCommand, peers: List[LedgerCommand]): LogErrOrF[List[LedgerKey]] = {
      for {
        branches <- iorF(getBranches(brcNoSet))
        subjects <- getTransitSubjects()
        leveled = peers.map(p => (levelOf(branches, p.brcNo), p))
        grouped = leveled.groupBy(_._1).values.toList.flatMap { p =>
          val oneX = LedgerCommand(one.brcNo, one.subjectNo, one.direct, p.map(_._2.amount).sum)
          val max = List(levelOf(branches, one.brcNo), p.head._1).max
          val sjtNo = subjects(max)
          List(
            LedgerKey(oneX, List(LedgerCommand(oneX.brcNo, sjtNo, oneX.direct.peer, oneX.amount))),
            LedgerKey(LedgerCommand(p.head._2.brcNo, sjtNo, oneX.direct, oneX.amount), p.map(_._2))
          )
        }
      } yield grouped
    }
    for {
      _ <- logF(DebugArgs(e))
      b <- getSubAcctInfo(e.acctId, e.subAcctNo)
      p <- iorF(b.product.base.entryName.check[NameRule])
      x <- getAccounting(p, e.ctx.trnCode, e.changeType)
      h <- iorF(foundHeadBranch())
      branches = Map[AccountingBranchType, BranchNo](
        Open -> b.openBrnNo,
        Quota -> b.quotaBrcNo,
        Operation -> e.ctx.oper.brcNo,
        Headquarter -> h.no
      )
      one = LedgerCommand(b.quotaBrcNo, x.subjectNo, x.direction, e.amount)
      peers = x.peers.map(a => LedgerCommand(branches(a.brcType), a.subjectNo, x.direction.peer, amounts(a.amtType)))
      _ <- iorF(assertF(e.amount == peers.map(_.amount).sum, DebitCreditNotEqual(one, peers)))
      brcNoSet = (List(one.brcNo) ++ peers.map(_.brcNo)).toSet
      commands <- if (brcNoSet.size > 1) transit(brcNoSet, one, peers) else iorF(resultF(List(LedgerKey(one, peers))))
    } yield AccountingCommand(e.ctx, commands)
  }
}